package com.xiaobai.frame.config.dict;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.xiaobai.common.utils.config.dict.DictConvert;
import com.xiaobai.common.utils.tool.StringUtils;
import com.xiaobai.frame.service.IDictService;
import com.xiaobai.frame.utils.RedisTool;
import com.xiaobai.model.entity.system.Dict;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.xiaobai.frame.config.redis.RedisKey.DICT_DATA;

@Component
@RequiredArgsConstructor
public class DictUtils {

    private final RedisTool redisTool;
    private final IDictService iDictService;
    private static final String DICT_FIELD_SUFFIX = "Text";

    public void convertDict(Object target) {
        if (target instanceof List) {
            List<?> objectList = ((List<?>) target);
            if (CollectionUtils.isNotEmpty(objectList)) {
                List<DictDefinition> dictDefinitions = getMetadata(objectList.get(0));
                if (CollectionUtils.isEmpty(dictDefinitions)) return;
                List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictKey()).collect(Collectors.toList());
                Map<String, Map<String, String>> dictMapMap = getDictMap(dictNames);
                objectList.forEach(t -> doConvertDict(t, dictDefinitions, dictMapMap));
            }
        } else {
            List<DictDefinition> dictDefinitions = getMetadata(target);
            if (CollectionUtils.isEmpty(dictDefinitions)) return;
            List<String> dictNames = dictDefinitions.stream().map(d -> d.getDict().dictKey()).collect(Collectors.toList());
            Map<String, Map<String, String>> dictMapMap = getDictMap(dictNames);
            doConvertDict(target, dictDefinitions, dictMapMap);
        }
    }

    /**
     * 仅获取一次Dict元数据，降低多次反射造成的性能消耗
     * @param target 目标实体类
     * @return Dict元数据
     */
    private static List<DictDefinition> getMetadata(Object target) {
        List<DictDefinition> dictDefinitions = new ArrayList<>();
        if (ClassUtils.isPrimitiveOrWrapper(target.getClass())
                || target instanceof Map || target instanceof String) {
            return dictDefinitions;
        }
        List<Field> fields = FieldUtils.getAllFieldsList(target.getClass());
        for (Field field : fields) {
            //判断这个字段上面是否有这个对应的注解
            DictConvert dictConvert = AnnotationUtils.getAnnotation(field, DictConvert.class);
            if (dictConvert != null) {
                // 如果不为空,就把这个数据参数存储下来
                DictDefinition dictDefinition = new DictDefinition();
                dictDefinition.setDict(dictConvert);
                dictDefinition.setField(field);
                dictDefinitions.add(dictDefinition);
            }
        }
        return dictDefinitions;
    }

    /**
     * 统一获取当前实体类涉及到的字典表数据，避免多次查询数据库造成的性能消耗
     * @param dictNames 字典表Key值
     * @return 字典表数据
     */
    @SneakyThrows
    private Map<String, Map<String, String>> getDictMap(List<String> dictNames) {
        Map<String, Map<String, String>> cacheMap = redisTool.getCacheMap(DICT_DATA);
        if (cacheMap == null) {
            List<Dict> dictList = iDictService.queryList(null);
            cacheMap = dictList.stream()
                .filter(dict -> StrUtil.isNotBlank(dict.getDictKey()))
                .collect(Collectors.groupingBy(Dict::getCode,
                    Collectors.toMap(
                        Dict::getDictKey, Dict::getDictValue,
                        (existing, replacement) -> existing)
                ));
            redisTool.setCacheMap(DICT_DATA, cacheMap);
        }
        return cacheMap.entrySet().stream()
            .filter(entry -> dictNames.contains(entry.getKey()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    }

    @SneakyThrows
    private void doConvertDict(Object target, List<DictDefinition> dictDefinitions,
                                      Map<String, Map<String, String>> dictMapMap) {
        for (DictDefinition dictDefinition : dictDefinitions) {
            DictConvert dict = dictDefinition.getDict();
            Field field = dictDefinition.getField();
            Map<String, String> dictMap = dictMapMap.get(dict.dictKey());
            String dictCode = String.valueOf(FieldUtils.readField(target, field.getName(), true));
            String dictField = StringUtils.isEmpty(dict.dictValue()) ? field.getName() + DICT_FIELD_SUFFIX : dict.dictValue();
            FieldUtils.writeField(target, dictField, dictMap.get(dictCode), true);
        }
    }

    @Data
    public static class DictDefinition {

        private DictConvert dict;

        private Field field;

    }

    @Data
    public static class SysDict {

        private String code;

        private String text;

        private String name;

    }

}
